
#define GL3_PROTOTYPES
#include <iostream>
#include <fstream>
#include <sstream>
#include <string.h>

#include <GL/glew.h>
#include "Shader.h"
#include "Matrix4.h"


namespace Krengine
{
	Shader::Shader( const std::string name )
		: shaderName( name ),
		progID(0),
		useGeomShader(false),
		vshader(), fshader(), gshader()
	{

		progID = 0;
	}
	//TODO perform cleanup if necessary
	//Shader::~Shader() {}


	//TODO: add exception throwing for possible errors
	//TODO: add checks for if the shader is used without being initialized
	bool Shader::init()
	{
		/* check if the shader has already been succesfully created */
		if ( glIsProgram( progID ) )
		{
			GLint progLinkStatus;
			glGetProgramiv(progID, GL_LINK_STATUS, &progLinkStatus);
			if ( progLinkStatus == GL_TRUE )
				return true;
		}

		/* build all shaders for use in the program */
		vshader = buildShader( GL_VERTEX_SHADER );
		fshader = buildShader( GL_FRAGMENT_SHADER );
		if ( useGeomShader )
			gshader = buildShader( GL_GEOMETRY_SHADER );

		/* create program for the shaders */
		progID = glCreateProgram();
		if ( progID == 0 )
			throw "Unknown error creating shader program";

		/* bind shaders to program */
		glAttachShader(progID, vshader);
		glAttachShader(progID, fshader);
		if ( useGeomShader )
			glAttachShader(progID, gshader);
		glLinkProgram(progID);
		glUseProgram(progID);

		return true;
	}


	GLuint Shader::buildShader( GLenum shaderType )
	{
		GLuint target;

		/* build the path string */
		std::stringstream ss( std::stringstream::in | std::stringstream::out );
		ss << "shaders\\" << shaderName << "\\" << shaderName;
		std::string path = ss.str();
		//std::string path = "shaders\\" + shaderName + "\\" + shaderName;

		std::cout << "path: " << path << std::endl;

		switch (shaderType)
		{
			case GL_VERTEX_SHADER:
				path += ".vert";
				break;
			case GL_GEOMETRY_SHADER:
				path += ".geom";
				break;
			case GL_FRAGMENT_SHADER:
				path += ".frag";
				break;
		}

		/* attempt to open shader source file */
		std::ifstream is;
		is.open ( path, std::ios::in );
		if ( is.fail() || !is.is_open() )
			throw "Unable to open shader sourcefile";

		/* get shader source */
		std::string source, buff;
		while ( getline(is, buff) )
			source += buff + "\n";
		is.close();


		target = glCreateShader( shaderType );
		if ( target == 0 )
			throw "Unknown error creating shader";

		/* link source and compile shader */
		const char* tempSource = source.c_str();
		glShaderSource(target, 1, &tempSource, NULL);
		glCompileShader(target);

		return target;
	}


	/**
	 * binds the attribute locations of used uniforms
	 */
	void Shader::setupUniforms()
	{
		/* bind custom attribute locations */
		glBindAttribLocation(progID, 0, "vPos");
		glBindAttribLocation(progID, 1, "clr");
		glBindAttribLocation(progID, 2, "vNorm");
	}


	/**
	 * sends all the used uniformdata to the shaderprogram
	 */
	void Shader::sendUniformData( const Matrix4& modelMat, const Matrix4& normalMat, const Matrix4& mvpMat )
	{
		glUseProgram(progID);
		glUniformMatrix4fv( glGetUniformLocation(progID, "mvMatrix"), 1, GL_FALSE, &modelMat.data[0] );
		glUniformMatrix4fv( glGetUniformLocation(progID, "normMatrix"), 1, GL_FALSE, &normalMat.data[0] );
		glUniformMatrix4fv( glGetUniformLocation(progID, "mvpMatrix"), 1, GL_FALSE, &mvpMat.data[0] );
	}


	/**
	 * deletes any used shaders and programs
	 */
	void Shader::cleanup()
	{
		glDeleteShader(vshader);
		glDeleteShader(fshader);
		if ( useGeomShader )
			glDeleteShader(gshader);
		glDeleteProgram(progID);
	}
}
